Türkçe

Portlar ve Adaptörler olarak da bilinen Hexagonal Mimari'nin uygulamalarınızın bakımını, test edilebilirliğini ve esnekliğini nasıl iyileştirebileceğini öğrenin.

Hexagonal Mimari: Portlar ve Adaptörler İçin Pratik Bir Rehber

Yazılım geliştirmenin sürekli gelişen ortamında, sağlam, bakımı yapılabilir ve test edilebilir uygulamalar oluşturmak çok önemlidir. Hexagonal Mimari, diğer adıyla Portlar ve Adaptörler, bir uygulamanın temel iş mantığını dış bağımlılıklarından ayırarak bu endişeleri ele alan bir mimari desendir. Bu rehber, Hexagonal Mimari'nin kapsamlı bir anlayışını, faydalarını ve dünya genelindeki geliştiriciler için pratik uygulama stratejilerini sunmayı amaçlamaktadır.

Hexagonal Mimari Nedir?

Alistair Cockburn tarafından ortaya atılan Hexagonal Mimari, uygulama çekirdeğinin iş mantığını dış dünyadan izole etme fikri etrafında döner. Bu izolasyon, portlar ve adaptörler kullanılarak gerçekleştirilir.

Bunu şu şekilde düşünün: çekirdek uygulama merkezde oturur, altıgen bir kabukla çevrilidir. Portlar bu kabuktaki giriş ve çıkış noktalarıdır ve adaptörler bu portlara takılarak çekirdeği dış dünyaya bağlar.

Hexagonal Mimari'nin Temel İlkeleri

Hexagonal Mimari'nin etkinliğini destekleyen birkaç temel ilke vardır:

Hexagonal Mimari Kullanmanın Faydaları

Hexagonal Mimari'yi benimsemek birçok avantaj sunar:

Hexagonal Mimari Uygulaması: Pratik Bir Örnek

Hexagonal Mimari'nin uygulamasını basitleştirilmiş bir kullanıcı kayıt sistemi örneğiyle açıklayalım. Netlik için varsayımsal bir programlama dili (Java veya C#'a benzer) kullanacağız.

1. Çekirdeği (Uygulamayı) Tanımlayın

Çekirdek uygulama, yeni bir kullanıcı kaydetmek için iş mantığını içerir.


// Core/UserService.java (veya UserService.cs)
public class UserService {
    private final UserRepository userRepository;
    private final PasswordHasher passwordHasher;
    private final UserValidator userValidator;

    public UserService(UserRepository userRepository, PasswordHasher passwordHasher, UserValidator userValidator) {
        this.userRepository = userRepository;
        this.passwordHasher = passwordHasher;
        this.userValidator = userValidator;
    }

    public Result<User, String> registerUser(String username, String password, String email) {
        // Kullanıcı girdisini doğrula
        ValidationResult validationResult = userValidator.validate(username, password, email);
        if (!validationResult.isValid()) {
            return Result.failure(validationResult.getErrorMessage());
        }

        // Kullanıcının zaten var olup olmadığını kontrol edin
        if (userRepository.findByUsername(username).isPresent()) {
            return Result.failure("Kullanıcı adı zaten mevcut");
        }

        // Parolayı hash'le
        String hashedPassword = passwordHasher.hash(password);

        // Yeni bir kullanıcı oluştur
        User user = new User(username, hashedPassword, email);

        // Kullanıcıyı depoya kaydet
        userRepository.save(user);

        return Result.success(user);
    }
}

2. Portları Tanımlayın

Çekirdek uygulamanın dış dünyayla etkileşim kurmak için kullandığı portları tanımlarız.


// Ports/UserRepository.java (veya UserRepository.cs)
public interface UserRepository {
    Optional<User> findByUsername(String username);
    void save(User user);
}

// Ports/PasswordHasher.java (veya PasswordHasher.cs)
public interface PasswordHasher {
    String hash(String password);
}

//Ports/UserValidator.java (veya UserValidator.cs)
public interface UserValidator{
  ValidationResult validate(String username, String password, String email);
}

//Ports/ValidationResult.java (veya ValidationResult.cs)
public interface ValidationResult{
  boolean isValid();
  String getErrorMessage();
}

3. Adaptörleri Tanımlayın

Çekirdek uygulamayı belirli teknolojilere bağlayan adaptörleri uygularız.


// Adapters/DatabaseUserRepository.java (veya DatabaseUserRepository.cs)
public class DatabaseUserRepository implements UserRepository {
    private final DatabaseConnection databaseConnection;

    public DatabaseUserRepository(DatabaseConnection databaseConnection) {
        this.databaseConnection = databaseConnection;
    }

    @Override
    public Optional<User> findByUsername(String username) {
        // JDBC, JPA veya başka bir veritabanı erişim teknolojisi kullanarak uygulama
        // ...
        return Optional.empty(); // Yer tutucu
    }

    @Override
    public void save(User user) {
        // JDBC, JPA veya başka bir veritabanı erişim teknolojisi kullanarak uygulama
        // ...
    }
}

// Adapters/BCryptPasswordHasher.java (veya BCryptPasswordHasher.cs)
public class BCryptPasswordHasher implements PasswordHasher {
    @Override
    public String hash(String password) {
        // BCrypt kütüphanesini kullanarak uygulama
        // ...
        return "hashedPassword"; //Yer tutucu
    }
}

//Adapters/SimpleUserValidator.java (veya SimpleUserValidator.cs)
public class SimpleUserValidator implements UserValidator {
  @Override
  public ValidationResult validate(String username, String password, String email){
    //Basit Doğrulama mantığı
     if (username == null || username.isEmpty()) {
            return new SimpleValidationResult(false, "Kullanıcı adı boş olamaz");
        }
        if (password == null || password.length() < 8) {
            return new SimpleValidationResult(false, "Parola en az 8 karakter uzunluğunda olmalıdır");
        }
        if (email == null || !email.contains("@")) {
            return new SimpleValidationResult(false, "Geçersiz e-posta formatı");
        }

        return new SimpleValidationResult(true, null);
  }
}

//Adapters/SimpleValidationResult.java (veya SimpleValidationResult.cs)
public class SimpleValidationResult implements ValidationResult {
  private final boolean valid;
  private final String errorMessage;

    public SimpleValidationResult(boolean valid, String errorMessage) {
        this.valid = valid;
        this.errorMessage = errorMessage;
    }
  @Override
  public boolean isValid(){
    return valid;
  }

  @Override
  public String getErrorMessage(){
    return errorMessage;
  }
}



//Adapters/WebUserController.java (veya WebUserController.cs)
//Yönlendiren Adaptör - web'den gelen istekleri işler
public class WebUserController {
    private final UserService userService;

    public WebUserController(UserService userService) {
        this.userService = userService;
    }

    public String registerUser(String username, String password, String email) {
        Result<User, String> result = userService.registerUser(username, password, email);
        if (result.isSuccess()) {
            return "Kayıt başarılı!";
        } else {
            return "Kayıt başarısız: " + result.getFailure();
        }
    }
}

4. Bileşim

Her şeyi bir araya getirme. Bu bileşimin (bağımlılık enjeksiyonu) tipik olarak uygulamanın giriş noktasında veya bir bağımlılık enjeksiyonu kapsayıcısı içinde gerçekleştiğini unutmayın.


//Ana sınıf veya bağımlılık enjeksiyon yapılandırması
public class Main {
    public static void main(String[] args) {
        // Adaptörlerin örneklerini oluşturun
        DatabaseConnection databaseConnection = new DatabaseConnection("jdbc:mydb://localhost:5432/users", "user", "password");
        DatabaseUserRepository userRepository = new DatabaseUserRepository(databaseConnection);
        BCryptPasswordHasher passwordHasher = new BCryptPasswordHasher();
        SimpleUserValidator userValidator = new SimpleUserValidator();

        // Çekirdek uygulamayı oluşturun, adaptörleri enjekte edin
        UserService userService = new UserService(userRepository, passwordHasher, userValidator);

        //Bir yönlendiren adaptör oluşturun ve servise bağlayın
        WebUserController userController = new WebUserController(userService);

        //Artık userController aracılığıyla kullanıcı kaydı isteklerini işleyebilirsiniz
        String result = userController.registerUser("john.doe", "P@sswOrd123", "john.doe@example.com");
        System.out.println(result);
    }
}



//DatabaseConnection yalnızca gösterim amacıyla basit bir sınıftır
class DatabaseConnection {
    private String url;
    private String username;
    private String password;

    public DatabaseConnection(String url, String username, String password) {
        this.url = url;
        this.username = username;
        this.password = password;
    }

    // ... veritabanına bağlanmak için yöntemler (kısa olması için uygulanmamıştır)
}

//Result sınıfı (işlevsel programlamadaki Either'a benzer)
class Result<T, E> {
    private final T success;
    private final E failure;
    private final boolean isSuccess;

    private Result(T success, E failure, boolean isSuccess) {
        this.success = success;
        this.failure = failure;
        this.isSuccess = isSuccess;
    }

    public static <T, E> Result<T, E> success(T value) {
        return new Result<>(value, null, true);
    }

    public static <T, E> Result<T, E> failure(E error) {
        return new Result<>(null, error, false);
    }

    public boolean isSuccess() {
        return isSuccess;
    }

    public T getSuccess() {
        if (!isSuccess) {
            throw new IllegalStateException("Sonuç bir hatadır");
        }
        return success;
    }

    public E getFailure() {
        if (isSuccess) {
            throw new IllegalStateException("Sonuç başarılıdır");
        }
        return failure;
    }
}

class User {
    private String username;
    private String password;
    private String email;

    public User(String username, String password, String email) {
        this.username = username;
        this.password = password;
        this.email = email;
    }

    // alıcılar ve ayarlayıcılar (kısa olması için atlanmıştır)

}

Açıklama:

Gelişmiş Hususlar ve En İyi Uygulamalar

Hexagonal Mimari'nin temel ilkeleri basit olsa da, akılda tutulması gereken bazı gelişmiş hususlar vardır:

Gerçek Dünya Hexagonal Mimari Örnekleri

Birçok başarılı şirket ve proje, sağlam ve bakımı yapılabilir sistemler oluşturmak için Hexagonal Mimari'yi benimsemiştir:

Zorluklar ve Ödünleşimler

Hexagonal Mimari önemli faydalar sunarken, ilgili zorlukları ve ödünleşimleri kabul etmek önemlidir:

Hexagonal Mimari'nin faydalarını ve zorluklarını projenizin özel gereksinimleri ve ekip yetenekleri bağlamında dikkatlice değerlendirmek çok önemlidir. Bu bir gümüş kurşun değildir ve her proje için en iyi seçim olmayabilir.

Sonuç

Portlar ve adaptörlere yaptığı vurguyla Hexagonal Mimari, bakımı yapılabilir, test edilebilir ve esnek uygulamalar oluşturmak için güçlü bir yaklaşım sağlar. Çekirdek iş mantığını harici bağımlılıklardan ayırarak, değişen teknolojilere ve gereksinimlere kolayca uyum sağlamanıza olanak tanır. Göz önünde bulundurulması gereken zorluklar ve ödünleşimler olsa da, Hexagonal Mimari'nin faydaları genellikle özellikle karmaşık ve uzun ömürlü uygulamalar için maliyetlerinden daha ağır basar. Bağımlılık ters çevirme ve açık arayüzler ilkelerini benimseyerek, daha dirençli, anlaşılması daha kolay ve modern yazılım ortamının taleplerini daha iyi karşılayabilecek sistemler oluşturabilirsiniz.

Bu rehber, Hexagonal Mimari'nin temel ilkelerinden pratik uygulama stratejilerine kadar kapsamlı bir genel bakış sunmuştur. Bu kavramları daha fazla araştırmanızı ve kendi projelerinizde uygulamanızı denemenizi teşvik ediyoruz. Hexagonal Mimari'yi öğrenme ve benimseme yatırımı, uzun vadede kesinlikle karşılığını verecek, daha yüksek kaliteli yazılımlara ve daha mutlu geliştirme ekiplerine yol açacaktır.

Sonuç olarak, doğru mimariyi seçmek projenizin özel ihtiyaçlarına bağlıdır. Kararınızı verirken karmaşıklık, uzun ömürlülük ve bakım gereksinimlerini göz önünde bulundurun. Hexagonal Mimari, sağlam ve uyarlanabilir uygulamalar oluşturmak için sağlam bir temel sağlar, ancak bu yalnızca bir yazılım mimarının araç kutusundaki bir araçtır.